﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace Qreed.Windows
{
    public class ShellFolder : IDisposable //: Shell32.IShellFolder
    {
        /// <summary>
        /// Indicates the type of files.
        /// </summary>
        [Flags]
        public enum ItemType : uint
        {
            /// <summary>
            /// Item is a folder.
            /// </summary>
            Folder = 0x0020,
            /// <summary>
            /// Item is a file.
            /// </summary>
            File = 0x0040,
            /// <summary>
            /// Item is hidden.
            /// </summary>
            Hidden = 0x0080
        }

        private static ShellFolder _desktopShellFolder = null;
        /// <summary>
        /// Gets the desktop folder.
        /// </summary>
        /// <value>The desktop folder.</value>
        internal static ShellFolder DesktopShellFolder
        {
            get
            {
                if (_desktopShellFolder == null)
                    _desktopShellFolder = new ShellFolder(ShellPIDL.DesktopPIDL);
                    
                return _desktopShellFolder;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ShellFolder"/> class.
        /// </summary>
        /// <param name="pidl">The pidl.</param>
        internal ShellFolder(ShellPIDL pidl)
        {
            // Should only be called by ShellPIDL

            if (pidl == null)
                throw (new ArgumentNullException("pidl cannot be null", "pidl"));

            if (!pidl.IsFolder)
                throw (new ArgumentException("Cannot create a ShellFolder object from a non folder pidl.", "pidl"));

            _pidl = pidl;

            if (pidl.IsDesktop)
            {
                Shell32.SHGetDesktopFolder(ref _interface);
            }
            else
            {
                ShellPIDL parentPIDL = pidl.Parent;
              
                uint res = DesktopShellFolder._interface.BindToObject(pidl.PIDL, IntPtr.Zero,
                                                                          ref Shell32.IID_IShellFolder,
                                                                          out _interface);
                if (res != 0) Marshal.ThrowExceptionForHR((int)res);
            }
        }
                
        private ShellPIDL _pidl;
        /// <summary>
        /// Gets the shell PIDL.
        /// </summary>
        /// <value>The shell PIDL.</value>
        public ShellPIDL ShellPIDL
        {
            get { return this._pidl; }
        }

        internal Shell32.IShellFolder _interface;

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);    
        }
        
        private void Dispose(bool disposing)
        {
            if (_interface != null)
            {
                Marshal.ReleaseComObject(_interface);
                _interface = null;
            }
        }

        /// <summary>
        /// Gets the children.
        /// </summary>
        /// <param name="flags">The flags.</param>
        /// <returns></returns>
        public ShellPIDL[] GetChildren(ItemType flags)
        {
            List<ShellPIDL> children = new List<ShellPIDL>();

            Shell32.IEnumIDList enumList = null;
            
            try
            {
                Shell32.SHCONTF includeFlags = (Shell32.SHCONTF)flags;

                uint res = _interface.EnumObjects(IntPtr.Zero, includeFlags, out enumList);
                if (res != 0) Marshal.ThrowExceptionForHR((int)res);

                if(enumList != null)
                {

                    IntPtr pIDL = IntPtr.Zero;
                    int fetched = 0;

                    try
                    {
                        enumList.Next(1, out pIDL, out fetched);

                        while (pIDL != IntPtr.Zero && fetched > 0)
                        {
                            ShellPIDL child = new ShellPIDL(ShellPIDL, pIDL);
                            children.Add(child);

                            Marshal.FreeCoTaskMem(pIDL);
                            pIDL = IntPtr.Zero;
                            fetched = 0;

                            enumList.Next(1, out pIDL, out fetched);
                        }
                    }
                    finally
                    {
                        if (pIDL != IntPtr.Zero) Marshal.FreeCoTaskMem(pIDL);
                    }
                }
            }
            finally
            {
                if (enumList != null) Marshal.ReleaseComObject(enumList);
            }

            return children.ToArray();
        }

        #endregion


      /*  #region IShellFolder Members
        
        /// <summary>
        /// Translates a file object's or folder's display name into an item identifier list.
        /// </summary>
        /// <param name="hwnd">Window handle. The client should provide a window handle if it displays a dialog or message box. Otherwise set hwnd to NULL.</param>
        /// <param name="pbc">Bind context that controls the parsing operation. This parameter is normally set to NULL.</param>
        /// <param name="pszDisplayName">Null-terminated Unicode string with the display name. Because each Shell folder defines its own parsing syntax, the form this string can take may vary. The desktop folder, for instance, accepts paths such as "C:\My Docs\My File.txt". It also will accept references to items in the namespace that have a globally unique identifier (GUID) associated with them using the "::{GUID}" syntax. For example, to retrieve a fully qualified identifier list for the control panel from the desktop folder, you can use the following:
        /// <code>::{CLSID for My Computer}\::{CLSID for the Control Panel}</code></param>
        /// <param name="pchEaten">Pointer to a ULONG value that receives the number of characters of the display name that was parsed. If your application does not need this information, set pchEaten to NULL, and no value will be returned.</param>
        /// <param name="ppidl">Pointer to an ITEMIDLIST pointer that receives the item identifier list for the object. The returned item identifier list specifies the item relative to the parsing folder. If the object associated with pwszDisplayName is within the parsing folder, the returned item identifier list will contain only one SHITEMID structure. If the object is in a subfolder of the parsing folder, the returned item identifier list will contain multiple SHITEMID structures. If an error occurs, NULL is returned in this address.</param>
        /// <param name="pdwAttributes">Value used to query for file attributes. If not used, it should be set to NULL. To query for one or more attributes, initialize the pdwAttributes with the flags that represent the attributes of interest. On return, those attributes that are true and were requested will be set. <see cref="SFGAO"/></param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint ParseDisplayName(IntPtr hwnd, IntPtr pbc, string pszDisplayName, out uint pchEaten, out IntPtr ppidl, ref Shell32.SFGAO pdwAttributes)
        {
            return _interface.ParseDisplayName(hwnd, pbc, pszDisplayName, out pchEaten, out ppidl, ref pdwAttributes);
        }

        /// <summary>
        /// Allows a client to determine the contents of a folder by creating an item identifier enumeration object and returning its IEnumIDList interface. The methods supported by that interface can then be used to enumerate the folder's contents.
        /// </summary>
        /// <param name="hwnd">If user input is required to perform the enumeration, this window handle should be used by the enumeration object as the parent window to take user input. An example would be a dialog box to ask for a password or prompt the user to insert a CD or floppy disk. If hwndOwner is set to NULL, the enumerator should not post any messages, and if user input is required, it should silently fail.</param>
        /// <param name="grfFlags">Flags indicating which items to include in the enumeration.</param>
        /// <param name="ppenumIDList">Address that receives a pointer to the IEnumIDList interface of the enumeration object created by this method. If an error occurs or no suitable subobjects are found, ppenumIDList is set to NULL.</param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint EnumObjects(IntPtr hwnd, Shell32.SHCONTF grfFlags, out Shell32.IEnumIDList ppenumIDList)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves an IShellFolder object for a subfolder.
        /// </summary>
        /// <param name="pidl">Address of an ITEMIDLIST structure (PIDL) that identifies the subfolder. pidl can refer to an object at any level below the parent folder in the namespace hierarchy. The pidl can thus be either a multi-level pointer to an item identifier list (PIDL), relative to the parent folder. The structure will contain one or more SHITEMID structures, followed by a terminating NULL.</param>
        /// <param name="pbc">Address of an IBindCtx interface on a bind context object to be used during this operation. If this parameter is not used, set it to NULL. Because support for pbc is optional for folder object implementations, some folders may not support the use of bind contexts.</param>
        /// <param name="riid">Identifier of the interface to return.</param>
        /// <param name="ppvOut">Address that receives the interface pointer. If an error occurs, a NULL pointer is returned in this address.</param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint BindToObject(IntPtr pidl, IntPtr pbc, ref Guid riid, out Shell32.IShellFolder ppvOut)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Requests a pointer to an object's storage interface.
        /// </summary>
        /// <param name="pidl">Address of an ITEMIDLIST structure that identifies the subfolder relative to its parent folder. The structure must contain exactly one SHITEMID structure followed by a terminating zero.</param>
        /// <param name="pbc">Optional address of an IBindCtx interface on a bind context object to be used during this operation. If this parameter is not used, set it to NULL. Because support for pbc is optional for folder object implementations, some folders may not support the use of bind contexts.</param>
        /// <param name="riid">Interface identifier (IID) of the requested storage interface. To retrieve an IStream, IStorage, or IPropertySetStorage interface pointer, set riid to IID_IStream, IID_IStorage, or IID_IPropertySetStorage, respectively.</param>
        /// <param name="ppvOut">Address that receives the interface pointer specified by riid. If an error occurs, a NULL pointer is returned in this address.</param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint BindToStorage(IntPtr pidl, IntPtr pbc, ref Guid riid, out object ppvOut)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Determines the relative order of two file objects or folders, given their item identifier lists.
        /// </summary>
        /// <param name="lParam">Value that specifies how the comparison should be performed.
        /// The lower sixteen bits of lParam define the sorting rule. Most applications set the sorting rule to the default value of zero, indicating that the two items should be compared by name. The system does not define any other sorting rules. Some folder objects might allow calling applications to use the lower sixteen bits of lParam to specify folder-specific sorting rules. The rules and their associated lParam values are defined by the folder.
        /// When the system folder view object calls IShellFolder::CompareIDs, the lower sixteen bits of lParam are used to specify the column to be used for the comparison.
        /// The upper sixteen bits of lParam are used for flags that modify the sorting rule. The system currently defines these modifier flags.</param>
        /// <param name="pidl1">Pointer to the first item's ITEMIDLIST structure. It will be relative to the folder. This ITEMIDLIST structure can contain more than one element; therefore, the entire structure must be compared, not just the first element.</param>
        /// <param name="pidl2">Pointer to the second item's ITEMIDLIST structure. It will be relative to the folder. This ITEMIDLIST structure can contain more than one element; therefore, the entire structure must be compared, not just the first element.</param>
        /// <returns>
        /// 	<![CDATA[If this method is successful, the CODE field of the HRESULT contains one of the following values. For information regarding the extraction of the CODE field from the returned HRESULT, see Remarks. If this method is unsuccessful, it returns an OLE error code.
        /// Negative A negative return value indicates that the first item should precede the second (pidl1 < pidl2).
        /// Positive A positive return value indicates that the first item should follow the second (pidl1 > pidl2).
        /// Zero A return value of zero indicates that the two items are the same (pidl1 = pidl2).
        /// ]]>
        /// </returns>
        public int CompareIDs(Shell32.SHCIDS lParam, IntPtr pidl1, IntPtr pidl2)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Requests an object that can be used to obtain information from or interact with a folder object.
        /// </summary>
        /// <param name="hwndOwner">Handle to the owner window. If you have implemented a custom folder view object, your folder view window should be created as a child of hwndOwner.</param>
        /// <param name="riid">Identifier of the requested interface.</param>
        /// <param name="ppvOut">Address of a pointer to the requested interface.</param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint CreateViewObject(IntPtr hwndOwner, ref Guid riid, out object ppvOut)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves the attributes of one or more file or folder objects contained in the object represented by IShellFolder.
        /// </summary>
        /// <param name="cidl">Number of items from which to retrieve attributes.</param>
        /// <param name="apidl">Address of an array of pointers to ITEMIDLIST structures, each of which uniquely identifies an item relative to the parent folder. Each ITEMIDLIST structure must contain exactly one SHITEMID structure followed by a terminating zero.</param>
        /// <param name="rgfInOut">Address of a single ULONG value that, on entry, contains the attributes that the caller is requesting. <see cref="SFGAO"/></param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint GetAttributesOf(int cidl, out IntPtr apidl, out Shell32.SFGAO rgfInOut)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves an OLE interface that can be used to carry out actions on the specified file objects or folders.
        /// </summary>
        /// <param name="hwndOwner">Handle to the owner window that the client should specify if it displays a dialog box or message box.</param>
        /// <param name="cidl">Number of file objects or subfolders specified in the apidl parameter.</param>
        /// <param name="apidl">Address of an array of pointers to ITEMIDLIST structures, each of which uniquely identifies a file object or subfolder relative to the parent folder. Each item identifier list must contain exactly one SHITEMID structure followed by a terminating zero.</param>
        /// <param name="riid">Identifier of the Component Object Model (COM) interface object to return. This can be any valid interface identifier that can be created for an item. The most common identifiers used by the Shell are listed in the comments at the end of this reference.</param>
        /// <param name="rgfReserved">Reserved.</param>
        /// <param name="ppvOut">Pointer to the requested interface. If an error occurs, a NULL pointer is returned in this address.</param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint GetUIObjectOf(IntPtr hwndOwner, int cidl, IntPtr[] apidl, ref Guid riid, IntPtr rgfReserved, out object ppvOut)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Retrieves the display name for the specified file object or subfolder.
        /// </summary>
        /// <param name="pidl">Address of an ITEMIDLIST structure (PIDL) that uniquely identifies the file object or subfolder relative to the parent folder.</param>
        /// <param name="uFlags">Flags used to request the type of display name to return. For a list of possible values, see the <see>SHGNO</see> enumerated type.</param>
        /// <param name="pName">Address of a STRRET structure in which to return the display name. The type of name returned in this structure can be the requested type, but the Shell folder might return a different type.</param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint GetDisplayNameOf(IntPtr pidl, Shell32.SHGNO uFlags, out Shell32.STRRET_FLAGS pName)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Sets the display name of a file object or subfolder, changing the item identifier in the process.
        /// </summary>
        /// <param name="hwnd">Handle to the owner window of any dialog or message boxes that the client displays.</param>
        /// <param name="pidl">Pointer to an ITEMIDLIST structure that uniquely identifies the file object or subfolder relative to the parent folder. The structure must contain exactly one SHITEMID structure followed by a terminating zero.</param>
        /// <param name="pszName">Pointer to a null-terminated string that specifies the new display name.</param>
        /// <param name="uFlags">Flags indicating the type of name specified by the lpszName parameter. For a list of possible values, see the description of the SHGNO enumerated type.</param>
        /// <param name="ppidlOut">Address of a pointer to an ITEMIDLIST structure that receives the new ITEMIDLIST. Implementations of IShellFolder::SetNameOf usually return a pointer to the new ITEMIDLIST in the ppidlOut parameter, but callers of this interface must be capable of handling the case where *ppidlOut is not set. If you call IShellFolder::SetNameOf with ppidlOut set to NULL, it will not return a new ITEMIDLIST for the object. If an error occurs, the implementation must set *ppidlOut to NULL (if ppidlOut is non-NULL).</param>
        /// <returns>
        /// Returns S_OK if successful, or an error value otherwise.
        /// </returns>
        public uint SetNameOf(IntPtr hwnd, IntPtr pidl, string pszName, Shell32.SHGNO uFlags, out IntPtr ppidlOut)
        {
            throw new NotImplementedException();
        }

        #endregion*/


        internal int CompareIDs(Shell32.SHCIDS sHCIDS, IntPtr _pIDL, IntPtr intPtr)
        {
            return _interface.CompareIDs(Shell32.SHCIDS.SHCIDS_ALLFIELDS, _pIDL, ShellPIDL.DesktopPIDL.PIDL);
        }
    }
}
